package org.nlp2rdf.navigator.api;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;
import org.dllearner.algorithms.ocel.OCEL;
import org.dllearner.core.LearningAlgorithm;
import org.dllearner.core.owl.Description;
import org.dllearner.parser.KBParser;
import org.dllearner.parser.ParseException;
import org.dllearner.refinementoperators.OperatorInverter;
import org.dllearner.refinementoperators.RefinementOperator;
import org.dllearner.refinementoperators.RhoDRDown;
import org.dllearner.utilities.experiments.Examples;
import org.nlp2rdf.navigator.client.common.ClassificationCountResultDTO;
import org.nlp2rdf.navigator.client.common.ClassificationResultDTO;
import org.nlp2rdf.navigator.client.common.ConceptDTO;
import org.nlp2rdf.navigator.client.common.ExampleDTO;
import org.nlp2rdf.navigator.client.common.KnowledgeBaseDescriptorDTO;
import org.nlp2rdf.navigator.client.common.LearnedConceptDTO;
import org.nlp2rdf.navigator.client.common.RelatedConceptsDTO;
import org.nlp2rdf.navigator.client.common.SearchDescriptorDTO;
import org.nlp2rdf.navigator.client.common.SearchResultDTO;
import org.nlp2rdf.navigator.client.common.StoredConceptDTO;
import org.nlp2rdf.navigator.client.common.LearnedConceptDTO.MyExamples;
import org.nlp2rdf.navigator.client.common.RelatedConceptsDTO.StrategyDTO;
import org.nlp2rdf.navigator.client.exception.NKEConceptParseException;
import org.nlp2rdf.navigator.client.exception.NKELearningException;
import org.nlp2rdf.navigator.client.exception.NKEQueryException;
import org.nlp2rdf.navigator.client.exception.NKERuntimeException;
import org.nlp2rdf.navigator.refactored.IClassifier;
import org.nlp2rdf.navigator.component.exception.ConceptSavingFailedException;
import org.nlp2rdf.navigator.component.learn.ILearn;
import org.nlp2rdf.navigator.component.resource.IResourceManager;
import org.nlp2rdf.navigator.component.search.ISearch;
import org.nlp2rdf.navigator.component.store.IStore;
import org.nlp2rdf.navigator.util.DTOHelper;
import org.nlp2rdf.navigator.util.ExceptionHelper;
import org.nlp2rdf.navigator.util.Prefixer;
import org.springframework.context.ApplicationContext;

import com.jamonapi.Monitor;
import com.jamonapi.MonitorFactory;

public class KnowledgeBase {
	private static final Logger logger = Logger.getLogger(KnowledgeBase.class);

	private IClassifier classifier;
	private List<ISearch> availableSearches;
	private IStore store;
	private IResourceManager resourceManager;
	private ILearn learn;

	private KnowledgeBaseDescriptorDTO knowledgeBaseDescriptorDTO;
	private Prefixer prefixer;

	public static KnowledgeBase getInstance(ApplicationContext context, String kbId) {
		KnowledgeBase s = (KnowledgeBase) context.getBean(kbId + ".session");
		s.initKnowledgeBaseDescriptorDTO();
		return s;
	}

	private void initKnowledgeBaseDescriptorDTO() {
		List<SearchDescriptorDTO> s = new ArrayList<SearchDescriptorDTO>();
		for (ISearch iSearch : availableSearches) {
			s.add(iSearch.getSearchDescriptor());
		}
		knowledgeBaseDescriptorDTO.setAvailableSearches(s);
	}

	public ClassificationCountResultDTO classifyCount(String conceptInKBSyntax)
			throws NKEConceptParseException, NKEQueryException, NKERuntimeException {
		ClassificationCountResultDTO ret = null;
		Monitor m = MonitorFactory.getTimeMonitor("classifyCount").start();
		try {
			ret = classifier.classifyCount(conceptInKBSyntax);
			ret.setMsNeeded(stopMon(m));
		} catch (RuntimeException e) {
			ExceptionHelper.handleRuntimeException(e, this.getClass());
		} finally {
			m.stop();
		}
		logger.info(knowledgeBaseDescriptorDTO.getKbId() + " classifyCount: " + ret);

		return ret;

	}

	/**
	 * limit and offset are directly forwarded to SPARQLdescriptionconverter ,
	 * whereas parameter<0 means no limit and no offset* @param
	 * conceptInKBSyntax
	 * 
	 * @param limit
	 * @param offset
	 * @return
	 * @throws NKEConceptParseException
	 * @throws NKEQueryException
	 * @throws NKERuntimeException
	 */
	public ClassificationResultDTO classify(String conceptInKBSyntax, int limit, int offset)
			throws NKEConceptParseException, NKEQueryException, NKERuntimeException {
		ClassificationResultDTO ret = null;
		Monitor m = MonitorFactory.getTimeMonitor("classify").start();
		try {

			ret = classifier.classify(conceptInKBSyntax, limit, offset);
			resourceManager.getLabelsForExamples(ret.getRetrievedExamples());
			resourceManager.getCommentsForExamples(ret.getRetrievedExamples());
			ret.setMsNeeded(stopMon(m));

		} catch (RuntimeException e) {
			ExceptionHelper.handleRuntimeException(e, getClass());
		} finally {
			m.stop();
		}
		logger.info(knowledgeBaseDescriptorDTO.getKbId() + "classify: " + ret);
		return ret;
	}

	public LearnedConceptDTO learnString(Collection<String> pos, Collection<String> neg)
			throws NKELearningException, NKEQueryException, NKERuntimeException {
		Examples ex = new Examples();
		ex.addPosTrain(pos);
		ex.addNegTrain(neg);
		return learn(ex);

	}

	//TODO
	public LearnedConceptDTO learn(List<ExampleDTO> pos, List<ExampleDTO> neg)
			throws NKELearningException, NKEQueryException, NKERuntimeException {
		
		Examples ex = new Examples();
		ex.addPosTrain(DTOHelper.toStringSet(pos));
		ex.addNegTrain(DTOHelper.toStringSet(neg));
		
		LearnedConceptDTO r =  learn(ex);
		r.setExamples(new MyExamples(pos, neg));
		return r;
	}

	public LearnedConceptDTO learn(Examples ex) throws NKELearningException, NKEQueryException,
			NKERuntimeException {
		

		Monitor m = MonitorFactory.getTimeMonitor("learn").start();
		LearnedConceptDTO learningResult = null;
		try {

			learningResult = learn.learn(ex);

			DTOHelper.refineLearningResult(learningResult, resourceManager, prefixer);

			learningResult.setMsNeeded(stopMon(m));
		} catch (RuntimeException e) {
			ExceptionHelper.handleRuntimeException(e, getClass());
		} finally {
			m.stop();
		}

		logger.info(knowledgeBaseDescriptorDTO.getKbId() + " learn: " + learningResult);
		return learningResult;
	}

	public SearchResultDTO search(String searchId, String searchString, int nrOfResults)
			throws NKEQueryException, NKERuntimeException {
		Monitor m = MonitorFactory.getTimeMonitor("search").start();
		SearchResultDTO searchResult = new SearchResultDTO();
		try {
			ISearch search = selectSearchById(searchId);

			searchResult.setMatching(search.searchOld(searchString, nrOfResults));

			// no negatives right now
			// ret.setNotMatching(search.getNegatives(searchString,
			// nrOfResults));

			resourceManager.getLabelsForExamples(searchResult.getMatching());
			resourceManager.getCommentsForExamples(searchResult.getMatching());

			// resourceManager.getLabelsForExamples(ret.getNotMatching());
			// resourceManager.getCommentsForExamples(ret.getNotMatching());

			searchResult.setMsNeeded(stopMon(m));

		} catch (RuntimeException e) {
			ExceptionHelper.handleRuntimeException(e, getClass());
		} finally {
			m.stop();
		}
		logger.info("searched for " + searchString + "\n" + searchResult);

		return searchResult;
	}

	public List<LearnedConceptDTO> refine(String conceptInKBSyntax, boolean specialise)
			throws NKELearningException, NKEQueryException, NKERuntimeException {
		Monitor m = MonitorFactory.getTimeMonitor("refine").start();
		List<LearnedConceptDTO> results = new LinkedList<LearnedConceptDTO>();
		try {
			LearningAlgorithm la = learn.getLearningAlgorithm();
			// method is specific to learning algorithm, so we need to perform a
			// class cast
			RhoDRDown operatorOrig = ((OCEL) la).getRefinementOperator();
			RefinementOperator operator;
			if (specialise) {
				// use operator directly
				operator = operatorOrig;
			} else {
				// invert the operator
				operatorOrig.setDropDisjuncts(true);
				operator = new OperatorInverter(operatorOrig);
			}
			Description d = KBParser.parseConcept(conceptInKBSyntax);
			Set<Description> refinements = operator.refine(d, d.getLength() + 2);
			if (!specialise) {
				operatorOrig.setDropDisjuncts(false);
			}

			for (Description refinement : refinements) {
				LearnedConceptDTO result = new LearnedConceptDTO();
				result.setKbSyntaxString(refinement.toKBSyntaxString());
				result.setManchesterSyntaxString(refinement.toManchesterSyntaxString(null, null));
				DTOHelper.refineLearningResult(result, resourceManager, prefixer);
				results.add(result);
			}
		} catch (RuntimeException e) {
			ExceptionHelper.handleRuntimeException(e, getClass());
		} catch (ParseException e) {
			logger.error(e);
			throw new NKELearningException("refinement failed, error in string");
		} finally {
			m.stop();
		}
		logger.info("" + results);

		return results;
	}

	public List<StoredConceptDTO> getStoredConcepts() throws NKERuntimeException {
		Monitor m = MonitorFactory.getTimeMonitor("getStoredConcepts").start();
		List<StoredConceptDTO> result = null;
		try {
			result = store.getStoredConcepts();
		} catch (RuntimeException e) {
			ExceptionHelper.handleRuntimeException(e, getClass());
		} finally {
			m.stop();
		}
		logger.debug("loaded " + result.size() + " concepts from store");
		return result;

	}

	public void saveConcept(LearnedConceptDTO learningResultDTO) throws NKERuntimeException {
		Monitor m = MonitorFactory.getTimeMonitor("saveConcept").start();
		try {
			store.saveConcept(learningResultDTO);
		} catch (ConceptSavingFailedException e) {
			ExceptionHelper.handleRuntimeException(e, getClass());
		} catch (RuntimeException e) {
			ExceptionHelper.handleRuntimeException(e, getClass());
		} finally {
			m.stop();
		}
		logger.debug("saved Concept");

	}

	public RelatedConceptsDTO getRelatedConcepts(ConceptDTO learningResultDTO)
			throws NKERuntimeException, NKEQueryException {
		Monitor m = MonitorFactory.getTimeMonitor("getRelatedConcepts").start();

		RelatedConceptsDTO relatedConcepts = null;
		try {
			relatedConcepts = store.getRelatedConcepts(learningResultDTO);
			relatedConcepts.setMsNeeded(stopMon(m));
			for (StrategyDTO s : relatedConcepts.getStrategies()) {
				for (ConceptDTO learningResult : s.getConcepts()) {
					DTOHelper.refineLearningResult(learningResult, resourceManager, prefixer);
				}
			}
			
			logger.error(relatedConcepts.toDebugString());

		} catch (RuntimeException e) {
			ExceptionHelper.handleRuntimeException(e, getClass());
		} finally {
			m.stop();
		}
		logger.info(relatedConcepts.toString());

		return relatedConcepts;

	}

	public void incrementHitCount(StoredConceptDTO learningResultDTO) throws NKERuntimeException {
		Monitor m = MonitorFactory.getTimeMonitor("incrementHitCount").start();
		try {
			store.incrementHitCount(learningResultDTO);
		} catch (RuntimeException e) {
			ExceptionHelper.handleRuntimeException(e, getClass());
		} finally {
			m.stop();
		}
		logger.info("incrementedHitCount");
	}

	private ISearch selectSearchById(String searchId) {
		for (ISearch iSearch : availableSearches) {
			if (iSearch.getSearchDescriptor().getSearchId().equals(searchId)) {
				return iSearch;
			}
		}
		return null;

	}

	public KnowledgeBaseDescriptorDTO getKnowledgeBaseDescriptorDTO() {
		return knowledgeBaseDescriptorDTO;
	}

	private static long stopMon(Monitor m) {
		m.stop();
		return Math.round(m.getLastValue());
	}

	public ILearn getLearn() {
		return learn;
	}

	public void setLearn(ILearn learn) {
		this.learn = learn;
	}

	public void setClassifier(IClassifier classifier) {
		this.classifier = classifier;
	}

	public void setResourceManager(IResourceManager resourceManager) {
		this.resourceManager = resourceManager;
	}

	public void setKnowledgeBaseDescriptorDTO(KnowledgeBaseDescriptorDTO knowledgeBaseDescriptorDTO) {
		this.knowledgeBaseDescriptorDTO = knowledgeBaseDescriptorDTO;
	}

	public void setPrefixer(Prefixer prefixer) {
		this.prefixer = prefixer;
	}

	public void setAvailableSearches(List<ISearch> availableSearches) {
		this.availableSearches = availableSearches;
	}

	public List<ISearch> getAvailableSearches() {
		return availableSearches;
	}

	public void setStore(IStore store) {
		this.store = store;
	}

	public IResourceManager getResourceManager() {
		return resourceManager;
	}

	public Prefixer getPrefixer() {
		return prefixer;
	}

}
